Defining Services to Send and Receive Messages
Services represent the
endpoints in Service Broker applications. You can think of them as the
glue that binds contracts with queues. This binding ensures that the
typed messages specified in the contract end up in the appropriate
queues.
Here is the DDL syntax for creating services:
CREATE SERVICE ServiceName
[AUTHORIZATION OwnerName]
ON QUEUE [SchemaName.]QueueName
[( ContractName | [ DEFAULT ] [ ,...n ] )] [;]
For this example, you need to create two services: the initiator in AdventureWorks2008 and the target in XCatMgmt.
This is the initiator in AdventureWorks2008:
USE AdventureWorks2008
GO
CREATE SERVICE
[//samspublishing.com/SS2008/SSB/Services/CatalogChangeInitiatorService]
AUTHORIZATION [SSBTestUserName]
ON QUEUE
Production.CatalogChangeAckQueue
([//samspublishing.com/SS2008/SSB/Contracts/BasicCatalogChangeContract])
And this is the target in XCatMgmt:
USE XCatMgmt
GO
CREATE SERVICE
[//samspublishing.com/SS2008/SSB/Services/CatalogMaintenanceService]
AUTHORIZATION [SSBTestUserName]
ON QUEUE
Publication.CatalogChangeQueue
([//samspublishing.com/SS2008/SSB/Contracts/BasicCatalogChangeContract])
As you can see, creating
services is simple. Now that all the plumbing is in place, you can begin
the dialog between the services.
Planning Conversations Between Services
A conversation
is a dialog between two services. The purpose of this dialog is, of
course, the sending and receiving of messages, which ultimately leads to
the completion of a task.
A powerful feature of
Service Broker messaging is that it guarantees exactly-once-in-order
(EOIO) messaging. This means that messages are sent exactly once;
there’s no chance that a message can be sent twice because of a system
issue, so the receiver doesn’t have to check whether a message has
already been processed. It also means that messages are always ordered
in their queue in the same order in which they were sent. (The queuing_order
column of the queue indicates this order.) Service Broker makes sure of
this, even in cases in which the send order somehow gets out of sync.
Transactions are
an integral part of Service Broker conversations. When a message is sent
within the scope of a transaction, it is not actually moved to the
destination queue unless the transaction commits. This has to do with
the fact that before being placed in a queue, messages are stored in
internal tables called transmission queues (which are viewable via the catalog view sys.transmission_queue).
Similarly, a message is not deleted from a queue after it is received
unless the transaction commits (except in cases in which the RETENTION flag for the queue is set to ON).
This point is very important because it means that any database
operations as well as any messaging operations belong to the same
transaction, and they are controlled by the same transactional system.
This is a unique feature of messaging with Service Broker and is part of
the rationale for having messaging built in to the database.
The BEGIN CONVERSATION DIALOG statement is the cornerstone of the process of creating conversations. It specifies the services participating (TO SERVICE and FROM SERVICE) and the contract to which they will be adhering during the dialog (ON CONTRACT).
It also enables the correlation of messages because it is the thread
that relates them to each other. This relationship is achieved through
the use of a conversation, or dialog, handle. A dialog handle is a variable of type uniqueidentifier that identifies the dialog.
You use the following syntax to start a dialog:
BEGIN DIALOG [ CONVERSATION ] @DialogHandle
FROM SERVICE InitiatingServiceName
TO SERVICE 'TargetServiceName'
[ , { 'service_broker_guid' | 'CURRENT DATABASE' } ]
[ ON CONTRACT ContractName ]
[ WITH
[ { RELATED_CONVERSATION = RelatedDialogHandle |
RELATED_CONVERSATION_GROUP = RelatedConversationGroupId } ]
[ [ , ] LIFETIME = DialogLifetimeInSeconds ]
[ [ , ] ENCRYPTION = { ON | OFF } ] ]
[ ; ]
The items in the syntax are as follows:
@DialogHandle— This is an output parameter of type uniqueidentifier that is returned by the statement.
InitiatingServiceName— This is the name of the (local) service acting as the initiator.
'TargetServiceName'— This is the name of the service acting as the target. Note that this is a case-sensitive string (technically of type nvarchar(256)),
for purposes of name resolution against non–SQL Server services (for
later extensions); a byte-level comparison is made for name resolution.
If this value is incorrectly provided, messages remain in the
transmission queue. Note that sys.transmission_queue.to_service_name holds this value.
A Service Broker globally unique identifier (GUID) may be optionally specified after 'TargetServiceName', and it is required when you are doing inter-database messaging . The 'CURRENT_DATABASE' string indicates the current Service Broker GUID.
ContractName— This is the name of the contract that the services use.
WITH—
This clause allows you to specify a related conversation group to which
the current conversation is related, either via a conversation handle
or a conversation group ID.
Note
When a new conversation
is created, in addition to being assigned a new conversation (or
dialog) handle, that conversation is also joined to a new conversation
group behind the scenes, unless the group ID of an existing conversation
group is specified.
Conversation
groups are incredibly important because queues are locked at the
conversation group level. A queue used by any services in a group of
related conversations is locked on that group during receives, ensuring
that messages are always received serially by all the services in the
group. BEGIN CONVERSATION DIALOG implicitly locks the conversation group it specifies (or the implied group it creates).
If locking did not work this
way, a service program could receive a message lower in the queue order
before a second instance of the same service program finished receiving a
message higher in the order. If that lower message needed data that was
dependent on the other uncommitted receive, you would end up with a
referential integrity issue.
It is thus a rather
questionable practice to spread related and/or dependent data across
multiple messages or to do so without doing the appropriate checks in
the code.
The following options are available for the WITH clause:
RELATED_CONVERSATION— This option relates the current conversation to the conversation group created for the specified conversation handle.
RELATED_CONVERSATION_GROUP— This
option relates the current conversation to the conversation group
created for the specified conversation group ID. (This has the same
effect as the RELATED_CONVERSATION keyword, with a different parameter.) If the value provided for RelatedConversationGroupId is invalid, a new conversation group is created to which the dialog is related.
LIFETIME— This option specifies the number of seconds for which the dialog will remain open; it defaults to the maximum value of int, which is approximately 68 years. If this option is specified, both services must call END DIALOG CONVERSATION before this time is up, or an error is raised.
ENCRYPTION—
This option specifies whether messages transmitted beyond the current
SQL Server instance (within the conversation) are encrypted. It defaults
to ON, meaning that message
transmissions between databases on different instances are encrypted by
default. Encryption requires the use of certificates.